import 'dart:async';
import 'dart:convert';
import 'package:meta/meta.dart';
import 'package:platform/platform.dart';
import 'package:http/http.dart' as http;
import 'package:flutter/services.dart';
import 'utils.dart';
import 'modules.dart';
export 'modules.dart';

class IapPay {

  // 单例
  static IapPay instance = IapPay(
      IapPay.private(const LocalPlatform()));

  static StreamController<PurchasedItem> _purchaseController;
  static Stream<PurchasedItem> get purchaseUpdated => _purchaseController.stream;

  static StreamController<PurchaseResult> _purchaseErrorController;
  static Stream<PurchaseResult> get purchaseError => _purchaseErrorController.stream;

  static StreamController<ConnectionResult> _connectionController;
  static Stream<ConnectionResult> get connectionUpdated => _connectionController.stream;

  static StreamController<String> _purchasePromotedController;
  static Stream<String> get purchasePromoted => _purchasePromotedController.stream;

  /// [MethodChannel]
  static const MethodChannel _channel =
  const MethodChannel('iap_pay');
  static MethodChannel get channel => _channel;

  final Platform _pf;
  final http.Client _httpClient;

  static Platform get _platform => instance._pf;
  static http.Client get _client => instance._httpClient;

  /// 析构函数
  factory IapPay(IapPay _instance) {
    instance = _instance;
    return instance;
  }

  @visibleForTesting
  IapPay.private(Platform platform, {http.Client client})
      : _pf = platform,
        _httpClient = client;

  static Future<String> get platformVersion async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }

  /// showLoading 显示Loading文字
  static Future<dynamic> showLoading(
      String message,
      ) async {
    return await _channel.invokeMethod("showLoading", <String, dynamic>{
      'message': message,
    });
  }

  /// just checks if the client can make payments.
  Future<String> get check async {
    if (_platform.isIOS) {
      await _setPurchaseListener();
      final String result = await _channel.invokeMethod('canMakePayments');
      return result;
    }
    throw PlatformException(
        code: _platform.operatingSystem, message: "platform not supported");
  }

  Future<String> get endConnection async {
    if (_platform.isIOS) {
      _removePurchaseListener();
      return 'no-ops in ios';
    }
    throw PlatformException(
        code: _platform.operatingSystem, message: "platform not supported");
  }

  /// Retrieves a list of products from the store on `iOS`,also returns subscriptions.
  Future<List<IAPItem>> getProducts(List<String> skus) async {
    if (skus == null || skus.contains(null)) return [];
    skus = skus.toList();
    if (_platform.isIOS) {
      dynamic result = await _channel.invokeMethod(
        'getItems',
        {
          'skus': skus,
        },
      );

      return extractItems(json.encode(result));
    }
    throw PlatformException(
        code: _platform.operatingSystem, message: "platform not supported");
  }

  /// Retrieves subscriptions on `iOS`, also returns non-subscription products.
  Future<List<IAPItem>> getSubscriptions(List<String> skus) async {
    if (skus == null || skus.contains(null)) return [];
    skus = skus.toList();
    if (_platform.isIOS) {
      dynamic result = await _channel.invokeMethod(
        'getItems',
        {
          'skus': skus,
        },
      );

      return extractItems(json.encode(result));
    }
    throw PlatformException(
        code: _platform.operatingSystem, message: "platform not supported");
  }

  /// Purchase history includes all types of products.
  Future<List<PurchasedItem>> getPurchaseHistory() async {
    if (_platform.isIOS) {
      dynamic result =
      await _channel.invokeMethod('getAvailableItems');

      return extractPurchased(json.encode(result));
    }
    throw PlatformException(
        code: _platform.operatingSystem, message: "platform not supported");
  }

  /// Request a purchase on `iOS`.
  /// Result will be received in `purchaseUpdated` listener or `purchaseError` listener.
  ///
  /// Identical to [requestSubscription] on `iOS`.
  Future requestPurchase(String sku, {
    String developerIdAndroid,
    String accountIdAndroid,
  }) async {
    if (_platform.isIOS) {
      return await _channel.invokeMethod(
          'buyProduct', <String, dynamic>{
        'sku': sku,
      });
    }
    throw PlatformException(
        code: _platform.operatingSystem, message: "platform not supported");
  }


  /// Add Store Payment (iOS only)
  /// Indicates that the App Store purchase should continue from the app instead of the App Store.
  ///
  /// @returns {Future<String>}
  Future<String> getPromotedProductIOS() async {
    if (_platform.isIOS) {
      String result = await _channel.invokeMethod('getPromotedProduct');
      return result;
    }
    return null;
  }


  /// Add Store Payment (iOS only)
  /// Indicates that the App Store purchase should continue from the app instead of the App Store.
  ///
  /// @returns {Future} will receive result from `purchasePromoted` listener.
  Future requestPromotedProductIOS() async {
    if (_platform.isIOS) {
      return await _channel.invokeMethod('requestPromotedProduct');
    }
    throw PlatformException(code: _platform.operatingSystem, message: "platform not supported");
  }

  /// Buy product with offer
  ///
  /// @returns {Future} will receive result from `purchaseUpdated` listener.
  Future requestProductWithOfferIOS(
      String sku, String forUser, String withOffer,
      ) async {
    if (_platform.isIOS) {
      return await _channel.invokeMethod('requestProductWithOfferIOS', <String, dynamic>{
        'sku': sku,
        'forUser': forUser,
        'withOffer': withOffer,
      });
    }
    throw PlatformException(code: _platform.operatingSystem, message: "platform not supported");
  }

  /// 购买有数量的商品 @returns {Future} will receive result from `purchaseUpdated` listener.
  Future requestPurchaseWithQuantityIOS(
      String sku, int quantity,
      ) async {
    if (_platform.isIOS) {
      return await _channel.invokeMethod('requestPurchaseWithQuantity', <String, dynamic>{
        'sku': sku,
        'quantity': quantity,
      });
    }
    throw PlatformException(code: _platform.operatingSystem, message: "platform not supported");
  }

  /// 在IOS中获取待处理的购买。@returns {Future<List<PurchasedItem>>}
  Future<List<PurchasedItem>> getPendingTransactionsIOS() async {
    if (_platform.isIOS) {
      dynamic result = await _channel.invokeMethod(
        'getPendingTransactions',
      );

      return extractPurchased(json.encode(result));
    }
    return [];
  }

  /// Finish a transaction on `iOS`,Call this after finalizing server-side validation of the reciept.
  Future<String> finishTransaction(PurchasedItem purchasedItem,
      { String developerPayloadAndroid, bool isConsumable }) async {
    if (_platform.isIOS) {
      String result = await _channel.invokeMethod('finishTransaction', <String, dynamic>{
        'transactionIdentifier': purchasedItem.transactionId,
      });
      return result;
    }
    throw PlatformException(
        code: _platform.operatingSystem, message: "platform not supported");
  }


  /// Clear all remaining transaction on `iOS`.
  Future<String> clearTransactionIOS() async {
    if (_platform.isIOS) {
      String result = await _channel.invokeMethod('clearTransaction');
      return result;
    }
    throw PlatformException(
        code: _platform.operatingSystem, message: "platform not supported");
  }

  /// 仅检索试图通过应用商店“iOS”购买的产品列表。
  Future<List<IAPItem>> getAppStoreInitiatedProducts() async {
    if (_platform.isAndroid) {
      return List<IAPItem>();
    } else if (_platform.isIOS) {
      dynamic result =
      await _channel.invokeMethod('getAppStoreInitiatedProducts');

      return extractItems(json.encode(result));
    }
    throw PlatformException(
        code: _platform.operatingSystem, message: "platform not supported");
  }

  /// Check if a subscription is active on `iOS`.
  ///
  /// This is a quick and dirty way to check if a subscription is active.
  /// It is highly recommended to do server-side validation for all subscriptions.
  /// This method is NOT secure and untested in production.
  Future<bool> checkSubscribed({
    @required String sku,
    Duration duration: const Duration(days: 30),
    Duration grace: const Duration(days: 3),
  }) async {
    assert(sku != null);
    if (_platform.isIOS) {
      var history = await getPurchaseHistory();

      for (var purchase in history) {
        Duration difference =
        DateTime.now().difference(purchase.transactionDate);
        if (difference.inMinutes <= (duration + grace).inMinutes &&
            purchase.productId == sku) return true;
      }

      return false;
    }else if (_platform.isAndroid) {
      return false;
    }
    throw PlatformException(
        code: _platform.operatingSystem, message: "platform not supported");
  }

  /// Validate receipt in ios
  ///
  /// Example:
  /// ```
  /// const receiptBody = {
  /// 'receipt-data': purchased.transactionReceipt,
  /// 'password': '******'
  /// };
  /// const result = await validateReceiptIos(receiptBody, false);
  /// console.log(result);
  /// ```
  Future<http.Response> validateReceipt({
    Map<String, String> receiptBody,
    bool isTest = true,
  }) async {
    assert(receiptBody != null);
    assert(isTest != null);

    final String url = isTest
        ? 'https://sandbox.itunes.apple.com/verifyReceipt'
        : 'https://buy.itunes.apple.com/verifyReceipt';
    return await http.post(
      url,
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      body: json.encode(receiptBody),
    );
  }

  Future _setPurchaseListener() async {
    if (_purchaseController == null) {
      _purchaseController = new StreamController.broadcast();
    }

    if (_purchaseErrorController == null) {
      _purchaseErrorController = new StreamController.broadcast();
    }

    if (_connectionController == null) {
      _connectionController = new StreamController.broadcast();
    }

    if (_purchasePromotedController == null) {
      _purchasePromotedController = new StreamController.broadcast();
    }

    _channel.setMethodCallHandler((MethodCall call) {
      switch (call.method) {
        case "purchase-updated":
          Map<String, dynamic> result = jsonDecode(call.arguments);
          _purchaseController.add(new PurchasedItem.fromJSON(result));
          break;
        case "purchase-error":
          Map<String, dynamic> result = jsonDecode(call.arguments);
          _purchaseErrorController.add(new PurchaseResult.fromJSON(result));
          break;
        case "connection-updated":
          Map<String, dynamic> result = jsonDecode(call.arguments);
          _connectionController.add(new ConnectionResult.fromJSON(result));
          break;
        case "iap-promoted-product":
          String productId = call.arguments;
          _purchasePromotedController.add(productId);
          break;
        default:
          throw new ArgumentError('Unknown method ${call.method}');
      }
      return null;
    });
  }

  Future _removePurchaseListener() async {
    if (_purchaseController != null) {
      _purchaseController
        ..add(null)
        ..close();
      _purchaseController = null;
    }
    if (_purchaseErrorController != null) {
      _purchaseErrorController
        ..add(null)
        ..close();
      _purchaseErrorController = null;
    }
  }

}
